package beautiful.butterfly.server.httpserver.handlers;


import beautiful.butterfly.server.httpserver.handlers.notchange.FileChannelProgressiveFutureListener;
import beautiful.butterfly.server.httpserver.kit.StringKit;
import beautiful.butterfly.server.httpserver.mvc.Constant;
import beautiful.butterfly.server.httpserver.mvc.http.Request;
import beautiful.butterfly.server.httpserver.mvc.http.Response;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.DefaultFileRegion;
import io.netty.handler.codec.http.*;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.stream.ChunkedFile;
import io.netty.util.CharsetUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.RandomAccessFile;
import java.net.URLConnection;
import java.text.SimpleDateFormat;
import java.util.Date;

import static io.netty.handler.codec.http.HttpResponseStatus.*;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;


public class StaticFileHandler implements IStaticFileHandler<Boolean> {


    public static Logger logger = LoggerFactory.getLogger(StaticFileHandler.class);

    StaticFileHandler() {
    }

    private static void error(ChannelHandlerContext channelHandlerContext, HttpResponseStatus httpResponseStatus) {
        FullHttpResponse fullHttpResponse = new DefaultFullHttpResponse(HTTP_1_1, httpResponseStatus, Unpooled.copiedBuffer("Failure: " + httpResponseStatus + "\r\n", CharsetUtil.UTF_8));
        fullHttpResponse.headers().set(HttpConst.CONTENT_TYPE, Constant.CONTENT_TYPE_TEXT);
        channelHandlerContext.writeAndFlush(fullHttpResponse).addListener(ChannelFutureListener.CLOSE);
    }


    private static void sendNotModified(ChannelHandlerContext channelHandlerContext) {
        FullHttpResponse fullHttpResponse = new DefaultFullHttpResponse(HTTP_1_1, NOT_MODIFIED);//304
        setDateHeader(fullHttpResponse);
        channelHandlerContext.writeAndFlush(fullHttpResponse).addListener(ChannelFutureListener.CLOSE);
    }


    private static void setDateHeader(FullHttpResponse fullHttpResponse) {
        fullHttpResponse.headers().set(HttpConst.DATE, "20180812");
    }


    private static void setContentTypeHeader(HttpResponse httpResponse, File file) {
        String contentType = StringKit.mimeType(file.getName());
        if (null == contentType) {
            contentType = URLConnection.guessContentTypeFromName(file.getName());
        }
        httpResponse.headers().set(HttpConst.CONTENT_TYPE, contentType);
    }

    public static Date format(String date, String pattern) {
        SimpleDateFormat dateTimeFormatter = new SimpleDateFormat(pattern);
        Date localDateTime = null;
        try {
            localDateTime = dateTimeFormatter.parse(date);
        } catch (Exception e) {

        }
        return localDateTime;
    }

    @Override
    public Boolean handle(ChannelHandlerContext channelHandlerContext, Request request, Response response) throws Exception {
        if (!HttpConst.METHOD_GET.equals(request.getMethod())) {
            error(channelHandlerContext, METHOD_NOT_ALLOWED);
            return false;
        }


        final String path = (String) request.getAttributeMap().get("$");
        File file = new File(path);
        // Cache Validation
        if (http304(channelHandlerContext, request, file.lastModified())) {
            return false;
        }

        RandomAccessFile randomAccessFile;
        try {
            randomAccessFile = new RandomAccessFile(file, "r");
        } catch (FileNotFoundException ignore) {
            error(channelHandlerContext, NOT_FOUND);
            return false;
        }

        long fileLength = randomAccessFile.length();

        HttpResponse httpResponse = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
        setContentTypeHeader(httpResponse, file);//类型
        //不实现文件缓存功能
        httpResponse.headers().set(HttpConst.CONTENT_LENGTH, fileLength + "");
        if (request.isKeepAlive()) {
            httpResponse.headers().set(HttpConst.CONNECTION, HttpConst.KEEP_ALIVE);
        }

        // Write the initial line and the setHeader.
        channelHandlerContext.write(httpResponse);

        // Write the content.
        ChannelFuture sendFileFuture;
        ChannelFuture lastContentFuture;
        if (channelHandlerContext.pipeline().get(SslHandler.class) == null) {
            sendFileFuture = channelHandlerContext.write(new DefaultFileRegion(randomAccessFile.getChannel(), 0, fileLength), channelHandlerContext.newProgressivePromise());
            // Write the end marker.
            lastContentFuture = channelHandlerContext.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);

        } else {
            sendFileFuture =
                    channelHandlerContext.writeAndFlush(new HttpChunkedInput(new ChunkedFile(randomAccessFile, 0, fileLength, 8192)), channelHandlerContext.newProgressivePromise());
            // HttpChunkedInput will write the end marker (LastHttpContent) for us.
            lastContentFuture = sendFileFuture;
        }

        sendFileFuture.addListener(FileChannelProgressiveFutureListener.build(randomAccessFile));

        // Decide whether to close the connection or not.
        if (!request.isKeepAlive()) {
            lastContentFuture.addListener(ChannelFutureListener.CLOSE);
        }
        return false;
    }

    private boolean http304(ChannelHandlerContext channelHandlerContext, Request request, long lastModified) throws Exception {
        // Cache Validation
        String ifMdf = request.getHeader(HttpConst.IF_MODIFIED_SINCE);
        if (StringKit.isBlank(ifMdf)) {
            return false;
        }

        Date ifModifiedSinceDate = format(ifMdf, Constant.HTTP_DATE_FORMAT);
        // Only compare up to the second because the datetime format we send to the client
        // does not have milliseconds
        long ifModifiedSinceDateSeconds = ifModifiedSinceDate.getTime() / 1000;

        if (lastModified < 0 && ifModifiedSinceDateSeconds <= System.currentTimeMillis() / 1000) {
            sendNotModified(channelHandlerContext);
            return true;
        }

        long fileLastModifiedSeconds = lastModified / 1000;
        if (ifModifiedSinceDateSeconds == fileLastModifiedSeconds) {
            sendNotModified(channelHandlerContext);
            return true;
        }
        return false;
    }

}